/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package idea.sfo.viewer.designer;


import designersfo.Cons;
import designersfo.Cons.CellInfo;
import designersfo.ConsV;
import designersfo.EmptyV;
import idea.sfo.viewer.graphics.AnswerField;
import idea.sfo.viewer.graphics.Drawable;
import java.awt.Point;
import java.beans.IntrospectionException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.TreeMap;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.util.Exceptions;


/**
 *
 * @author renke002
 */
public class DesignerElementChildren extends Children.Keys<DesignerElement>
{

    /**
     * Integer defining the first element of an array.
     */
    public final static int FIRST_ELEMENT = 0;
    /**
     *
     */
    private Point anchorPoint;
    /**
     * Mapping between the column number and its width.
     * <p/>
     * Due to the structure of the SFO file, the widths of all the columns are
     * stored as attributes to the last row (of null/empty cells).
     */
    private TreeMap< Integer , Long> colWidths;
    /**
     * Mapping between a sequence number and a drawable (graphic) element.
     */
    private TreeMap<Integer , Drawable> graphicsMap;
    /**
     * Mapping between a Row Number, its Column Numbers and the corresponding
     * CellInfo objects.
     * <p/>
     * This map is intended to closely match the structure of the original SFO
     * XML file, so that each row matches a <gids-designer:cons-v> element and
     * each column matches a <gids-designer:cons-v>.
     * <p/>
     * The <gids-designer:cell-info> object is the deepest we can go into the
     * SFO structure without loosing any significant information.
     */
    private TreeMap< Integer , TreeMap< Integer , CellInfo>> rowColMap;
    /**
     * Mapping between the row number and its height.
     */
    private TreeMap< Integer , Long> rowHeigths;


    /**
     *
     * @param docRoot Root element of the JAXB XML document.
     */
    public DesignerElementChildren( List<?> docRoot )
    {
        //
        this.colWidths = new TreeMap<>();
        //
        this.rowHeigths = new TreeMap<>();
        //
        this.rowColMap = new TreeMap<>();
        //
        this.anchorPoint = new Point( 0 , 0 );
        // populate the maps above.
        this.parseDocRoot( docRoot );
    }


    /**
     *
     * @param docRoot Root element of the JAXB XML document.
     */
    public DesignerElementChildren( List<?> docRoot , Point anchor )
    {
        //
        this.colWidths = new TreeMap<>();
        //
        this.rowHeigths = new TreeMap<>();
        //
        this.rowColMap = new TreeMap<>();
        //
        this.anchorPoint = anchor;
        // populate the maps above.
        this.parseDocRoot( docRoot );
    }


    /**
     * Computes a set of Children.Keys<DesignerRow>
     */
    @Override
    protected void addNotify()
    {
        super.addNotify();
        /*
         * need to call this.setKeys( node[] )
         */
        if ( null == this.graphicsMap )
        {
            this.graphicsMap = this.buildGraphicsMap();
        }
        DesignerElement[] dElts = new DesignerElement[ this.graphicsMap.size() ];
        dElts = this.graphicsMap.values().toArray( dElts );
        //
        this.setKeys( dElts );
    }


    /**
     *
     */
    private TreeMap<Integer , Drawable> buildGraphicsMap()
    {
        /*
         * ASSUMPTION: this.colWidths this.rowHeigths this.rowColMap Have all
         * been created, instantiated, filled, etc.
         */
        TreeMap<Integer , Drawable> drawableObject = new TreeMap<>();

        DesignerElement dElt;
        for ( Integer rowNumber : this.rowColMap.keySet() )
        {
            for ( Integer colNumber : this.rowColMap.get( rowNumber ).keySet() )
            {
                CellInfo cInfo = this.rowColMap
                        .get( rowNumber )
                        .get( colNumber );
                /*
                 */
                if ( cInfo.getCustomContentCellData().isEmpty() == false )
                {
                    if ( Integer.parseInt( cInfo.getJoinedCellCountH() ) == 1
                            && Integer.parseInt( cInfo.getJoinedCellCountV() ) == 1 )
                    {
                        // this is a standalone cell 
                        dElt = new DesignerElement(
                                cInfo ,
                                rowNumber ,
                                colNumber ,
                                this.rowHeigths ,
                                this.colWidths ,
                                this.anchorPoint );
                        //
                        drawableObject.put( drawableObject.size() , dElt );
                    }
                    else
                    {
                        // this cell is part of a joined set of cells, they will
                        // need to be handled differently.
                        dElt = new DesignerElement(
                                cInfo ,
                                rowNumber ,
                                colNumber ,
                                this.rowColMap ,
                                this.rowHeigths ,
                                this.colWidths ,
                                this.anchorPoint );
                        //
                        drawableObject.put( drawableObject.size() , dElt );
                    }
                }
            }
        }

        return drawableObject;
    }


    /**
     * Create a list of Nodes for a given key.
     * <p/>
     * @param dElt the key
     * @return child nodes for this key or null if there are none.
     */
    @Override
    protected Node[] createNodes( DesignerElement dElt )
    {
        return new Node[]
        {
            new DesignerElementNode( dElt )
        };
//        try
//        {
//            return new Node[]
//            {
//                new DesignerElementNode( dElt )
//            };
//        }
//        catch ( IntrospectionException ex )
//        {
//            Exceptions.printStackTrace( ex );
//        }
//        return null;
    }


    public TreeMap<Integer , Long> getColWidths()
    {
        return this.colWidths;
    }


    public TreeMap<Integer , Long> getRowHeights()
    {
        return this.rowHeigths;
    }


    /**
     *
     * @return
     */
    public ArrayList<Drawable> getGraphics()
    {
        if ( null == this.graphicsMap )
        {
            this.graphicsMap = this.buildGraphicsMap();
        }
        return new ArrayList<>( this.graphicsMap.values() );
    }


    /**
     * Parse a 'designersfo.Cons' element and its children to build a column #
     * -> cons map and populate the row heights map.
     * <p/>
     * @param cons
     * @return a cl
     */
    private TreeMap<Integer , CellInfo> parseConsElement( Cons cons )
    {
        TreeMap<Integer , CellInfo> map = new TreeMap<>();

        // loop through all this element's children nodes until empty.
        while ( cons != null )
        {
            /*
             * As per the Designer Memento Spec on the wiki page :
             * https://censuswiki.fenestra.com/bin/view/Main/DesignerMementoSpec
             * a <gids-designer:cons> should contain either a
             * <gids-designer:cell-info> <gids-designer:cons> pair or a
             * <gids-designer:row-info> <gids-designer:empty-list> pair.
             *
             * However things change so we'll still check if this cons contains
             * either pair and assign it's child to itself anyway since the
             * while condition handles those cases.
             */
            if ( cons.getCellInfo().isEmpty() == false )
            {
                // this cons contains a cell-info element
                map.put( map.size() , cons.getCellInfo().get( FIRST_ELEMENT ) );
            }
            else if ( cons.getRowInfo().isEmpty() == false )
            {
                /*
                 * This cons is at the end of a row, let's get the row height
                 * from the 'row-info' element's 'cell-size-v' attribute.
                 */
                String fu_size = cons.getRowInfo().get( FIRST_ELEMENT ).
                        getCellSizeV();
                //
                this.rowHeigths.put(
                        this.rowHeigths.size() ,
                        Long.parseLong( fu_size.replace( "fu" , "" ) ) );
            }

            /*
             * Continue the loop by assigning one of this element's children to
             * the holder variable.
             */
            if ( cons.getCons().isEmpty() == false )
            {
                cons = cons.getCons().get( FIRST_ELEMENT );
            }
            else
            {
                cons = null;
            }

        }
        return map;
    }


    /**
     * Parse the root element of the XML tree.
     * <p/>
     * @param docRoot root element of the XML tree.
     */
    private void parseDocRoot( List<?> docRoot )
    {
        //
        ConsV row;
        //
        Object vat;

        //
        Iterator<?> cvs_it = docRoot.iterator();
        while ( cvs_it.hasNext() )
        {
            /*
             * the list holds several different objects, so we need to cast each
             * one to Object and then check it with 'intanceof' to see which one
             * we need to process.
             */
            vat = cvs_it.next();

            if ( vat instanceof ConsV )
            {
                row = (ConsV) vat;

                // new row parse the columns within.
                if ( row.getCons().isEmpty() == false )
                {
                    this.rowColMap.put(
                            new Integer( rowColMap.size() ) ,
                            this.parseConsElement( row.getCons().get(
                            FIRST_ELEMENT ) ) );
                }

                if ( row.getEmptyV().isEmpty() == false )
                {
                    this.parseEmptyVList( row.getEmptyV().get( FIRST_ELEMENT ) );
                }

                if ( row.getConsV().isEmpty() == false )
                {
                    this.parseDocRoot( row.getConsV() );
                }

            }
            else if ( vat instanceof Cons )
            {
                //
                this.rowColMap.put(
                        new Integer( rowColMap.size() ) ,
                        this.parseConsElement( (Cons) vat ) );
            }
            else if ( vat instanceof EmptyV )
            {
                /*
                 * represents the end-of-column marker cells located at the
                 * bottom of the designer grid, they contain 'col-info' cells
                 * that store the width of the column they represent.
                 */
                this.parseEmptyVList( (EmptyV) vat );
            }
            else
            {
                System.err.println( "In 'DesignerElementChildren'" );
            }
        }
    }


    /**
     * Parse a list of EmptyV objects.
     * <p/>
     * @param ev
     */
    private void parseEmptyVList( EmptyV ev )
    {
        if ( ev.getColInfo().isEmpty() == false )
        {
            String fu_size = ev.getColInfo().get( FIRST_ELEMENT ).getCellSizeH();
            this.colWidths.put( this.colWidths.size() , new Long( fu_size.
                    replace( "fu" , "" ) ) );
        }
        /*
         * an EmptyV can contain a reference to a child EmptyV so we need to
         * check for a child, and call this function recursively on it if found.
         */
        if ( ev.getEmptyV().isEmpty() == false )
        {
            parseEmptyVList( ev.getEmptyV().get( FIRST_ELEMENT ) );
        }
    }

}
